package com.liuhouse.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.liuhouse.constants.SystemCanstants;
import com.liuhouse.dto.AddArticleDto;
import com.liuhouse.pojo.Article;
import com.liuhouse.mapper.ArticleMapper;
import com.liuhouse.pojo.ArticleTag;
import com.liuhouse.pojo.Category;
import com.liuhouse.service.IArticleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liuhouse.service.IArticleTagService;
import com.liuhouse.service.ICategoryService;
import com.liuhouse.utils.BeanCopyUtils;
import com.liuhouse.utils.RedisCache;
import com.liuhouse.utils.ResponseResult;
import com.liuhouse.vo.*;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 文章表 服务实现类
 * </p>
 *
 * @author liuhouse
 * @since 2024-01-27
 */
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements IArticleService {

    @Resource
    private ICategoryService categoryService;

    @Resource
    private IArticleTagService articleTagService;

    @Resource
    private RedisCache redisCache;

    @Override
    public ResponseResult hotArticleList() {
        //查询热门文章,封装成ResponseReult对象返回,吧所有的条件都写在queryWrapper中
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        //查询的不能是草稿,也就是status字段不能为1 0:已发布 1:草稿
        queryWrapper.eq(Article::getStatus, SystemCanstants.ARTICLE_STATUS_NORMAL);
        //按照浏览量进行排序,也就是按照viewCount字段进行降序排序
        queryWrapper.orderByDesc(Article::getViewCount);
        //上面的查询条件已经构建完毕
        //下面进行分页查询
        //最多查询十条消息,当前显示第一页的数据,每页显示十条
        Page<Article> page = new Page<>(SystemCanstants.ARTICLE_STATUS_CURRENT, SystemCanstants.ARTICLE_STATUS_SIZE);
        Page<Article> page_result = this.page(page, queryWrapper);
        //获取最终的查询结果,把结果封装在Article实体类里面会有很多不需要的字段
        List<Article> articles = page_result.getRecords();
        articles.stream()
                .map(new Function<Article, Article>() {
                    @Override
                    public Article apply(Article article) {
                        Integer viewCount = redisCache.getCacheMapValue("article:viewCount", article.getId().toString());
                        return article.setViewCount(viewCount.longValue());
                    }
                }).collect(Collectors.toList());
        //解决：把结果封装在HotArticleVO实体类里面,在HotArticleVO实体类只写我们的字段
//        ArrayList<HotArticleVO> articleVOS = new ArrayList<>();
//        for(Article article : articles){
//            //定义我们自己的文章vo类
//            HotArticleVO hotArticleVO = new HotArticleVO();
//            //使用spring提供的BeanUtils类,实现bean的拷贝,第一个是源数据,第二个参数是目标数据
//            //把源数据拷贝给目标数据
//            //虽然article里面有很多不同的字段,但是hotArticleVO里面只有三个字段(没有具体数据)
//            //所以,拷贝之后就能把hotArticleVO里面的三个字段填充具体的数据
//            //article就是Article的每一个的实体类 ， hotArticleVO就是 hotArticleVO 的实体类
//            BeanUtils.copyProperties(article , hotArticleVO);
//            articleVOS.add(hotArticleVO);
//        }
        //一行代码就搞定
        List<HotArticleVO> hotArticleVOS = BeanCopyUtils.copyBeanList(articles, HotArticleVO.class);
        return ResponseResult.okResult(hotArticleVOS);
    }



    /**
     * 分页查询文章列表数据
     *
     * @param pageNum    当前的页码数
     * @param pageSize   每页显示的数量
     * @param categoryId 分类id
     * @return
     */
    @Override
    public ResponseResult articleList(Integer pageNum, Integer pageSize, Long categoryId) {
        //1.构建查询条件
        LambdaQueryWrapper<Article> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //_1.判断categoryId是否为空不为空才加入判断中
        lambdaQueryWrapper.eq(Objects.nonNull(categoryId) && categoryId > 0, Article::getCategoryId, categoryId);
        //_2.只是查询正式发布的文章
        //只是查询状态是正式发布的文章。Article实体类的status字段跟0作比较,一样就表示是正式发布的
        lambdaQueryWrapper.eq(Article::getStatus, SystemCanstants.ARTICLE_STATUS_NORMAL);
        //_3.对isTop字段进行排序,实现置顶的文章(isTop值为1)在最前面
        lambdaQueryWrapper.orderByDesc(Article::getIsTop);

        //2.进行分页查询
        Page<Article> page = new Page<>(pageNum, pageSize);
        this.page(page, lambdaQueryWrapper);

        /**
         * 解决categeoryName字段没有返回值的问题，在分页之后,封装成ArticleListVo之前,进行处理
         */
        //用categoryId来查询categoryName(category表的name字段)，也就是查询'分类名称',有两种方式实现
        List<Article> articles = page.getRecords();
        //第一种方式,使用for循环遍历的方式
        //这里面对变量的修改 是会将原始数据修改的  因为是同一个变量地址 Page变量
        /*
            for(Article article : articles){
                //article.getCategoryId() 表示从article表中获取category_id字段,然后作为查询category表的name字段
                Category category = categoryService.getById(article.getCategoryId());
                //把查询出来的category表的name字段值，也就是article , 设置给Article实体类的categoryName属性值
                article.setCategoryName(category.getName());
            }
        */


        //第二种方式,使用stream流的方式
        /*
        articles.stream()
                .map(new Function<Article, Article>() {
                    @Override
                    public Article apply(Article article) {
                        Category category = categoryService.getById(article.getCategoryId());
                        String name = category.getName();
                        article.setCategoryName(name);
                        return article;
                    }
                }).collect(Collectors.toList());
         */

        //第二种方式stream流简化版
        articles.stream()
                .map(article -> {
                    //这边的缓存的  浏览量是整数 因为 不是整数的话  不能递增  所以  是整数
                    Integer viewCount = redisCache.getCacheMapValue("article:viewCount", String.valueOf(article.getId()));
                    article.setViewCount(viewCount.longValue());
                    return article.setCategoryName(categoryService.getById(article.getCategoryId()).getName());
                })
                .collect(Collectors.toList());

        //3.封装文章vo实体类
        //把最后查询结果封装成ArticleListVo(我们自己写的实体类),BeanCopyUtils是我们写的工具类
        List<ArticleListVo> articleListVos = BeanCopyUtils.copyBeanList(page.getRecords(), ArticleListVo.class);
        //封装分页实体类
        //把上面那行的查询结果和文章总数封装在PageVo中(我们自己写实体类)
        PageVo pageVo = new PageVo(articleListVos, page.getTotal());
        return ResponseResult.okResult(pageVo);
    }

    /**
     * 获取文章详情
     * @param id
     * @return
     */
    @Override
    public ResponseResult getArticleDetail(Long id) {
       //根据id查询文章
        Article article = getById(id);
        //把最后的查询的结果封装成ArticleDetailVo(我们自己写的实体类),BeanCopyUtils是我们自己写的工具类
        ArticleDetailVo articleDetailVo = BeanCopyUtils.copyBean(article, ArticleDetailVo.class);
        //根据分类id,来查询分类名称
        //获取到文章的分类id
        Long categoryId = articleDetailVo.getCategoryId();
        //查询分类名称
        Category category = categoryService.getById(categoryId);
        //如果根据分类的id查询到分类名称,那么就把查询到的值设置给ArticleDetailVo实体类的categoryName字段
        if(category != null){
            articleDetailVo.setCategoryName(category.getName());
        }
        
        //其实最好都是在redis进行读取 不然的话浏览量不能保持一致,其实也是一个问题
        //从redis中查找文章浏览量 然后进行在详情页面的显示
        Integer viewCount = redisCache.getCacheMapValue("article:viewCount", id.toString());
        articleDetailVo.setViewCount(Long.valueOf(viewCount));


        //封装响应返回,ResponseResult是我们在liuhouse-framework工程的domain目录写的实体类
        return ResponseResult.okResult(articleDetailVo);
    }

    @Override
    public ResponseResult updateViewCount(Long id) {
        //更新redis中的浏览量,对应文章id的viewCount浏览量
        //article:viewCount是ViewCountRunner类里面写的缓存的键
        //用户从mysql根据文章id查询一次浏览量,那么redis的浏览量就增加1
        redisCache.incrementCacheMapValue("article:viewCount" , id.toString() , 1);
        return ResponseResult.okResult();
    }

    //添加博客   添加博客关联的标签 这里因为要添加两张表的数据  所以 要成功 都成功 要失败都失败  所以这里使用到了事务
    @Override
    @Transactional
    public ResponseResult add(AddArticleDto articleDto) {
        //添加博客
        //1.将传递过来的ArticleDto转化为Article对象
        Article article = BeanCopyUtils.copyBean(articleDto, Article.class);
        //2.获取到标签数组,然后添加博客和标签的关联
        this.save(article);
        //获取博客中的标签数组
        List<Long> tags = articleDto.getTags();
        List<ArticleTag> articleTags = tags.stream()
                .map(tagId -> new ArticleTag(article.getId(), tagId))
                .collect(Collectors.toList());
        //添加博客和标签的关联
        articleTagService.saveBatch(articleTags);
        return ResponseResult.okResult();
    }

    @Override
    public PageVo selectArticlePage(Article article, Integer pageNum, Integer pageSize) {
       //1.构建查询条件
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        //对文章名称进行模糊查询
        queryWrapper.like(StringUtils.hasText(article.getTitle()) , Article::getTitle , article.getTitle());
        //对摘要进行模糊查询
        queryWrapper.like(StringUtils.hasText(article.getSummary()), Article::getSummary , article.getSummary());
        //2.进行查询
        Page<Article> page = new Page<>(pageNum, pageSize);
        this.page(page , queryWrapper);
        List<Article> records = page.getRecords();
        long total = page.getTotal();
        //3进行数据返回
        PageVo pageVo = new PageVo(records , total);
        return pageVo;
    }

    /**
     * 根据文章Id查询文章详情
     * @param id
     * @return
     */
    @Override
    public ArticleByIdVo getInfo(Long id) {
        //获取文章的基本信息
        Article article = getById(id);
        //获取关联的标签
        LambdaQueryWrapper<ArticleTag> articleTagLambdaQueryWrapper = new LambdaQueryWrapper<>();
        articleTagLambdaQueryWrapper.eq(ArticleTag::getArticleId , id);
        //获取所有文章对应的标签列表对象信息
        List<ArticleTag> articleTags = articleTagService.list(articleTagLambdaQueryWrapper);
        //得到对应的的标签id然后进行返回
        List<Long> tags = articleTags.stream()
                .map(articleTag -> articleTag.getTagId())
                .collect(Collectors.toList());
        //将文章的bean转化为要回显的对象
        ArticleByIdVo ArticleVo = BeanCopyUtils.copyBean(article, ArticleByIdVo.class);
        //将文章标签数据返回到文章对象的vo中
        ArticleVo.setTags(tags);
        return ArticleVo;
    }


    //进行文章修改
    @Transactional
    @Override
    public void edit(AddArticleDto articleDto) {
        //对传递过来的dto数据转化为Article对象
        Article article = BeanCopyUtils.copyBean(articleDto, Article.class);
        //更新博客信息
        updateById(article);
        //接下来需要更新文章的标签信息
        //思考一：传递过来的数据和原来已经有的数据进行对比 哪些有哪些没有 然后去添加或者修改或者删除 真的是很麻烦
        //思考二:将原来的所有的标签的数据进行删除,然后添加全新的数据  【这种事比较清晰的,就使用这种】 怎么方便怎么来
        //删除原来的标签
        LambdaQueryWrapper<ArticleTag> removeQueryWrapper = new LambdaQueryWrapper<>();
        removeQueryWrapper.eq(ArticleTag::getArticleId , article.getId());
        articleTagService.remove(removeQueryWrapper);
        //添加新的博客和标签之间的关联关系
        //先要获取传递过来的文章的标签信息
        List<ArticleTag> articleTags = articleDto.getTags().stream()
                .map(tagId -> new ArticleTag(article.getId(), tagId))
                .collect(Collectors.toList());
        articleTagService.saveBatch(articleTags);

    }


    //删除文章信息
    //因为是删除多张表的数据  所以  这里需要进行事务处理
    @Transactional
    @Override
    public void deleteArticleById(Long id) {
        //1.删除文章信息
        this.removeById(id);
        //2.删除标签信息
        LambdaQueryWrapper<ArticleTag> articleTagLambdaQueryWrapper = new LambdaQueryWrapper<>();
        articleTagLambdaQueryWrapper.eq(ArticleTag::getArticleId , id);
        articleTagService.remove(articleTagLambdaQueryWrapper);
    }
}
